<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1 id="grade">0分</h1>
  <div id="map" style="position: relative; width: 400px; height: 400px; background-color: #000;"></div>
  <br>
  <button id="start">开始游戏</button>
  <button id="stop">暂停游戏</button>

  <script>
    // 自定义事件
    class MyEvent{
      constructor(){
        this.events = {};
      }

      // 触发一个事件
      dispatch(evevtName, ...args){
        if(!this.events[evevtName]){
          return;
        }
        this.events[evevtName].call(this, ...args);
        // this.events[evevtName].map(item => item.call(this, ...args));
      }

      // 添加一个事件处理
      on(evevtName, fn){
        this.events[evevtName] = fn;
        // if(!this.events[evevtName]){
        //   this.events[evevtName] = [];
        // }

        // if(!this.events[evevtName].includes(fn)){
        //   this.events[evevtName].push(fn);
        // }
      }

      // 删除一个事件处理
      off(evevtName, fn){
        if(!this.events[evevtName]){
          return;
        }

        delete this.events[evevtName];
        // this.events[evevtName] = this.events[evevtName].filter(item => item != fn);
      }
    }
    // 地图类
    class Map{
      constructor(el, rect = 10){
        this.el = el;
        this.rect = rect;
        // 对应地图的行列
        this.rows = Math.ceil(Map.getStyle(this.el, 'width') / this.rect);
        this.cols = Math.ceil(Map.getStyle(this.el, 'height') / this.rect);
        // 修正地图的宽高
        Map.setStyle(this.el, 'width', this.rows * this.rect);
        Map.setStyle(this.el, 'height', this.cols * this.rect);
        
        // 要渲染的数据
        this.data = [];
      }

      static getStyle(el, attr){
        return parseFloat(getComputedStyle(el)[attr]);
      }

      static setStyle(el, attr, val){
        el.style[attr] = val + 'px';
      }

      // 地图初始化
      render(){
        this.el.innerHTML = this.data.map(item => {
          return `
            <span style="position: absolute; left: ${item.x * this.rect}px; top: ${item.y * this.rect}px; width: ${this.rect}px; height: ${this.rect}px; background-color: ${item.color}"></span>
          `;
        }).join('');
      }

      // 设置数据
      setData(val){
        this.data = this.data.concat(val);
      }

      // 清除数据
      clearData(){
        this.data.length = 0;
      }
    }

    // 蛇类
    class Snake{
      constructor() {
        // 蛇的长度
        this.data = [
          { x: 6, y: 2, color: 'green'},
          { x: 5, y: 2, color: 'white'},
          { x: 4, y: 2, color: 'white'},
          { x: 3, y: 2, color: 'white'},
          { x: 2, y: 2, color: 'white'}
        ];
        // 移动方向
        this.direction = 'right';
      }

      // 移动
      move(){
        let i = this.data.length - 1;

        // 最后一个点（蛇尾）
        this.lastData = {
          x: this.data[i].x,
          y: this.data[i].y,
          color: this.data[i].color
        }

        // 将每一个点向前移动一次
        for(i; i > 0; i--){
            this.data[i].x = this.data[i - 1].x;
            this.data[i].y = this.data[i - 1].y;
        }
        // 处理第一个位置
        switch(this.direction){
          case 'top':
            this.data[0].y--;
          break;
          case 'bottom':
            this.data[0].y++;
            break;
          case 'left':
            this.data[0].x--;
            break;
          case 'right':
            this.data[0].x++;
            break;
        }
      }

      isEatFood(food){
        return (this.data[0].x === food.x) && (this.data[0].y === food.y)
      }

      // 吃食物
      eatFood(){
        this.data.push(this.lastData);
      }

      // 改变方向
      changeDir(dir){
        // 当direction为左或右时
        if (this.direction === 'left' || this.direction === 'right'){
          if(dir === 'left' || dir === 'right'){
            return ;
          }
        } else {
          if(dir === 'top' || dir === 'bottom'){
            return ;
          }
        }

        this.direction = dir;
      }
    }

    // 食物类
    class Food{
      constructor(rows, cols){
        this.rows = rows;
        this.cols = cols;
        this.colors = ['red', 'yellow', 'blue', 'pink'];
        this.data = {}

        this.create();
      }

      // 创建食物
      create(){
        let index = Math.floor(Math.random() * this.colors.length);

        this.data = {
          x: Math.floor(Math.random() * this.rows),
          y: Math.floor(Math.random() * this.cols),
          color: this.colors[index]
        }
      }
    }

    // 游戏控制类
    class Game extends MyEvent{
      constructor(el, rect = 10, /*toGrade = null,*/ toOver = null, toWin = null){
        super();
        this.el = el;
        this.rect = rect;
        // this.toGrade = toGrade;
        this.toOver = toOver;
        this.toWin = toWin;
        this.map = new Map(this.el, rect);
        this.snake = new Snake();
        this.food = new Food(this.map.rows, this.map.cols);

        // 定时器
        this.timer = null;
        this.interval = 200;

        // 分数
        this.winGrade = 25;
        this.grade = 0;

        this.initGame();
      }

      // 分数改变回调
      // toGrade(val){
      //   this.toGrade && this.toGrade(val);
      // }
      // 游戏结束回调
      toOver(){
        this.toOver && this.toOver();
      }
      // 游戏通关回调
      toWin(){
        this.toWin && this.toWin();
      }

      // 初始化游戏
      initGame(){
        this.map.setData(this.snake.data);
        this.map.setData(this.food.data);
        this.map.render();
      }

      // 移动
      move(){
        this.timer = setInterval(() => {
          this.map.clearData();
          this.snake.move();
          if(this.snake.isEatFood(this.food.data)){
            this.snake.eatFood();
            this.food.create();
            this.grade++;
            // this.toGrade(this.grade);
            this.dispatch('tograde', this.grade);
            this.interval = this.interval * 0.9;
            this.stop();
            this.start();
          }
          if(this.isOver()){
            this.stop();
            this.toOver();
            this.door = false;
            return;
          }
          this.map.setData(this.snake.data);
          this.map.setData(this.food.data);
          this.map.render();
          if(this.grade >= this.winGrade){
            this.toWin();
            this.stop();
          }
        }, this.interval);
      }

      // 是否结束
      isOver(){
        let snakeData = this.snake.data;
        // 第一种撞到自己
        for(let i = 1; i < snakeData.length; i++){
          if(snakeData[i].x === snakeData[0].x
          && snakeData[i].y === snakeData[0].y){
            return true;
          }
        }
        // 第二种撞到墙壁
        if(snakeData[0].x < 0
        || snakeData[0].x >= this.map.rows
        || snakeData[0].y < 0
        || snakeData[0].y >= this.map.cols){
          return true;
        }
      }

      // 游戏开始
      start(){
        this.move();
        this.control();
      }
      // 游戏暂停
      stop(){
        clearInterval(this.timer);
      }
      // 键盘控制
      control(){
        window.onkeydown = ({ keyCode }) => {
          switch(keyCode){
            case 38:
              this.snake.changeDir('top');
            break;
            case 40:
              this.snake.changeDir('bottom');
            break;
            case 37:
              this.snake.changeDir('left');
            break;
            case 39:
              this.snake.changeDir('right');
            break;
          }
        }
      }
    }

    {
      let el = document.getElementById('map');
      let start = document.getElementById('start');
      let stop = document.getElementById('stop');
      let grade = document.getElementById('grade');
      let game = new Game(el, 12);
      start.onclick = function(){
        game.start();
      }
      stop.onclick = function(){
        game.stop();
      }
      // game.toGrade = function(val){
      //   grade.innerHTML = val + '分';
      // }
      game.on('tograde', function(val) {
        grade.innerHTML = val + '分';
      })
      // game.off('tograde');
      game.toOver = function(){
        alert('游戏结束');
      }
      game.toWin = function(){
        console.log('恭喜你，通关了！');
      }
    }
    
  </script>
</body>
</html>